import math
from mpmath import norm


class StandardBarrierOptions(object):
    """ Class for standard barrier option valuation using Black-Scholes-Merton model.

    Attributes
    ==========
    OptionType : string
        ‘cdi’ for call down-and-in, ‘cui’ for call up-and-in,
        ‘cdo’ for call down-and-out, ‘cuo’ for call up-and-out,
        ‘pdi’ for put down-and-in, ‘pui’ for put up-and-in,
        ‘pdo’ for put down-and-out, ‘puo’ for put up-and-out
    S0 : float
        initial stock/index level
    K : float
        strike price
    T : float
        maturity (in year fractions)
    r : float
        constant risk-free short rate
    sigma : float
        volatility factor in diffusion term
    H : float
        barrier level
    barrier_monitoring : string
                观察频率；
                c = continuously，随时观察
                h = hourly，每小时观察
                d = daily，每日观察
                w = weekly，每周观察
                m = monthly，每月观察
    b : float
        cost of carry
    cash_rebate : float
        cash rebate when barrier is reached
    Methods
    =======
    value : float
        return present value of barrier option
    delta : float
        return delta of barrier option
    gamma : float
        return gamma of barrier option
    theta : float
        return theta of barrier option
"""
    def __init__(self, OptionType, S0, K, T, r, sigma, H,barrier_monitoring='c', b=0, cash_rebate=0):
        self.OptionType = OptionType
        self.S0 = float(S0)
        self.K = K
        self.T = T
        self.r = r
        self.sigma = sigma
        self.H = H
        self.barrier_monitoring = barrier_monitoring
        self.b = b
        self.cash_rebate = cash_rebate

    def adjustedBarrier(self):
        """
        根据观察频率调整障碍值
        :return:返回经调整后的障碍值
        """
        if self.barrier_monitoring == 'c':
            dt = 0
        elif self.barrier_monitoring == 'h':
            dt = 1/(24*365)
        elif self.barrier_monitoring == 'd':
            dt = 1/365
        elif self.barrier_monitoring == 'w':
            dt = 7/365
        elif self.barrier_monitoring == 'm':
            dt = 1/12
        else:
            print("Error: Barrier monitoring type not valid.")
            return

        if self.H < self.S0:
            adjusted_barrier = self.H * math.exp(-0.5826 *self.sigma * math.sqrt(dt))
        elif self.H >= self.S0:
            adjusted_barrier = self.H * math.exp(0.5826 *self.sigma * math.sqrt(dt))

        return adjusted_barrier

    def value(self):
        """
        返回障碍期权价格
        :return: 障碍期权价格
        """
        adjustedH = self.adjustedBarrier()

        mu = (self.b - 0.5 * self.sigma ** 2) / (self.sigma ** 2)
        lamda = math.sqrt(mu ** 2 + 2 * self.r / (self.sigma ** 2))
        X1 = math.log(self.S0 / self.K) / (self.sigma * math.sqrt(self.T)) + (1 + mu) * self.sigma * math.sqrt(self.T)
        X2 = math.log(self.S0 / adjustedH) / (self.sigma * math.sqrt(self.T)) + (1 + mu) * self.sigma * math.sqrt(self.T)
        Y1 = math.log(adjustedH ** 2 / (self.S0 * self.K)) / (self.sigma * math.sqrt(self.T)) + (1 + mu) * self.sigma * math.sqrt(self.T)
        Y2 = math.log(adjustedH / self.S0) / (self.sigma * math.sqrt(self.T)) + (1 + mu) * self.sigma * math.sqrt(self.T)
        Z = math.log(adjustedH / self.S0) / (self.sigma * math.sqrt(self.T)) + lamda * self.sigma * math.sqrt(self.T)

        if self.OptionType == 'cdi' or self.OptionType == 'cdo':
            eta = 1
            phi = 1
        elif self.OptionType == 'cui' or self.OptionType == 'cuo':
            eta = -1
            phi = 1
        elif self.OptionType == 'pdi' or self.OptionType == 'pdo':
            eta = 1
            phi = -1
        elif self.OptionType == 'pui' or self.OptionType == 'puo':
            eta = -1
            phi = -1
        else:
            print("Error: Option type not valid.")
            return

        f1 = (phi * self.S0 * math.exp((self.b - self.r) * self.T) * norm.cdf(phi * X1)
              - phi * self.K * math.exp(-self.r * self.T)
               * norm.cdf(phi * X1 - phi * self.sigma * math.sqrt(self.T)))
        f2 = (phi * self.S0 * math.exp((self.b - self.r) * self.T) * norm.cdf(phi * X2)
              - phi * self.K * math.exp(-self.r * self.T)
              * norm.cdf(phi * X2 - phi * self.sigma * math.sqrt(self.T)))
        f3 = (phi * self.S0 * math.exp((self.b - self.r) * self.T) * (adjustedH / self.S0) ** (2 * (mu + 1))* norm.cdf(eta * Y1)
              - phi * self.K * math.exp(-self.r * self.T) * (adjustedH / self.S0) ** (2 * mu)
              * norm.cdf(eta * Y1 - eta * self.sigma * math.sqrt(self.T)))
        f4 = (phi * self.S0 * math.exp((self.b - self.r) * self.T) * (adjustedH / self.S0) ** (2 * (mu + 1)) * norm.cdf(eta * Y2)
              - phi * self.K * math.exp(-self.r * self.T) * (adjustedH / self.S0) ** (2 * mu)
              * norm.cdf(eta * Y2 - eta * self.sigma * math.sqrt(self.T)))
        f5 = self.cash_rebate * math.exp(-self.r * self.T) * (norm.cdf(eta * X2 - eta * self.sigma * math.sqrt(self.T))
                                                             - (adjustedH / self.S0) ** (2 * mu) * norm.cdf(eta * Y2 - eta * self.sigma * math.sqrt(self.T)))
        f6 = self.cash_rebate * ((adjustedH/self.S0) ** (mu + lamda) * norm.cdf(eta * Z)
                                 + (adjustedH/self.S0) ** (mu - lamda) * norm.cdf(eta * Z - 2 * eta * lamda * self.sigma * math.sqrt(self.T)))


        if adjustedH < self.K :
            if self.OptionType == 'cdi': value = f3 + f5
            elif self.OptionType == 'cui': value = f1 + f5
            elif self.OptionType == 'pdi': value = f2 - f3 + f4 + f5
            elif self.OptionType == 'pui': value = f1 - f2 + f4 + f5
            elif self.OptionType == 'cdo': value = f1 - f3 + f6
            elif self.OptionType == 'cuo': value = f6
            elif self.OptionType == 'pdo': value = f1 - f2 + f3 - f4 + f6
            elif self.OptionType == 'puo': value = f2 - f4 + f6
            else:
                print("Error: Option type not valid.")
                return
        elif adjustedH >= self.K:
            if self.OptionType == 'cdi': value = f1 - f2 + f4 + f5
            elif self.OptionType == 'cui': value = f2 - f3 + f4 + f5
            elif self.OptionType == 'pdi': value = f1 + f5
            elif self.OptionType == 'pui': value = f3 + f5
            elif self.OptionType == 'cdo': value = f2 + f6 - f4
            elif self.OptionType == 'cuo': value = f1 - f2 + f3 - f4 + f6
            elif self.OptionType == 'pdo': value = f6
            elif self.OptionType == 'puo': value = f1 - f3 + f6
            else:
                print("Error: Option type not valid.")
                return

        return value

    def delta(self):
        """
        将S0微小变动，计算障碍期权的delta
        :return: 返回障碍期权的delta
        """
        dS = 0.0001
        # 先将原价值保存
        original_value = self.value()
        # S0微小变动
        self.S0 += dS
        delta = (self.value() - original_value) / dS
        self.S0 -= dS

        return delta

